/**
* Seadragon Touch
* Copyright (c) Microsoft Corporation. All rights reserved.
* For more information, visit: http://seadragon.com/ajax/
*/
Seadragon.TouchController = function(viewer) {
    // States

    // Base (zero fingers)
    var BaseState = function(owner) {
        // Variables
        var owner = owner;

        this.init = function() {
            if (owner.debugInit) {
                Seadragon.Debug.log("BaseState::init");
            }

            // Do nothing
        }

        this.dispose = function() {
            if (owner.debugDispose) {
                Seadragon.Debug.log("BaseState::dispose");
            }

            // Do nothing
        };

        // Touch
        this.onTouchStart = function(event) {
            if (owner.debugTouchStart) {
                Seadragon.Debug.log("BaseState::onTouchStart");
            }

            var numTouches = event.touches.length;
            var viewport = owner.viewer.viewport;
            owner.touchPixelOrigin = owner.getTouchPixelOrigin(event);
            owner.lastTouchPoint = viewport.getCenter();
            owner.zoom = viewport.getZoom();

            // One finger down, switch to one finger tapping
            if (numTouches == 1) {
                owner.setCurrentState(owner.TAP_STATE);
                return;
            }

            // Two fingers down, switch to two finger tapping
            if (numTouches == 2) {
                owner.setCurrentState(owner.TWO_FINGER_TAP_STATE);
            }
        };

        this.onTouchMove = function(event) {
            if (owner.debugTouchMove) {
                Seadragon.Debug.error("BaseState::onTouchMove");
            }

            // Do nothing
        };

        this.onTouchEnd = function(event) {
            if (owner.debugTouchEnd) {
                Seadragon.Debug.error("BaseState::onTouchEnd");
            }

            // Do nothing
        };

        // Gestures
        this.onGestureStart = function(event) {
            if (owner.debugGestureStart) {
                Seadragon.Debug.error("BaseState::onGestureStart");
            }

            // Do nothing
        };

        this.onGestureChange = function(event) {
            if (owner.debugGestureChange) {
                Seadragon.Debug.error("BaseState::onGestureChange");
            }

            // Do nothing
        };

        this.onGestureEnd = function(event) {
            if (owner.debugGestureEnd) {
                Seadragon.Debug.error("BaseState::onGestureEnd");
            }

            // Do nothing
        };

        this.toString = function() {
            return "BaseState";
        };
    };

    // Tapping (one finger)
    var TapState = function(owner) {
        // Variables
        var owner = owner;
        var timerID = null;

        this.init = function() {
            if (owner.debugInit) {
                Seadragon.Debug.log("TapState::init");
            }
            timerID = setTimeout(this.timeoutHandler, owner.TAP_TIMEOUT);
        };

        this.dispose = function() {
            if (owner.debugDispose) {
                Seadragon.Debug.log("TapState::dispose");
            }
            if (timerID) {
                clearTimeout(timerID);
            }
        };

        this.timeoutHandler = function() {
            if (timerID) {
                clearTimeout(timerID);
                timerID = null;
            }

            owner.setCurrentState(owner.DRAG_STATE);
        };

        // Touch
        this.onTouchStart = function(event) {
            if (owner.debugTouchStart) {
                Seadragon.Debug.error("TapState::onTouchStart");
            }

            var numTouches = event.touches.length;

            if (numTouches >= 1) {
                owner.setCurrentState(owner.PINCH_STATE);
                return;
            }

            if (numTouches >= 2) { // assert
                Seadragon.Debug.error("TapState::onTouchStart (numTouches == " + numTouches + ") >= 2");
            }
        };

        this.onTouchMove = function(event) {
            if (owner.debugTouchMove) {
                Seadragon.Debug.log("TapState::onTouchMove");
            }

            var numTouches = event.touches.length;

            // Go to dragging state if touch moved too far (threshold)
            var newTouchPixelOrigin = owner.getTouchPixelOrigin(event);
            var distance = newTouchPixelOrigin.distanceTo(owner.touchPixelOrigin);

            if (distance > owner.TAP_DISTANCE_TRESHOLD) {
                owner.setCurrentState(owner.DRAG_STATE);
            }
        };

        this.onTouchEnd = function(event) {
            if (owner.debugTouchEnd) {
                Seadragon.Debug.log("TapState::onTouchEnd");
            }

            var numTouches = event.touches.length;

            if (numTouches != 0) { // assert
                Seadragon.Debug.error("TapState::onTouchEnd numTouches != 0");
            }

            var viewport = viewer.viewport;
            var touchPointOrigin = viewport.pointFromPixel(owner.touchPixelOrigin);
            viewport.zoomBy(owner.TAP_ZOOM_FACTOR, touchPointOrigin);
            viewport.ensureVisible();

            // TODO Is this the correct zoom value?
            owner.zoom = viewport.getZoom();

            // Go back to base state
            owner.setCurrentState(owner.BASE_STATE);
        };

        // Gestures
        this.onGestureStart = function(event) {
            if (owner.debugGestureStart) {
                Seadragon.Debug.log("TapState::onGestureStart");
            }

            // Do nothing
        };

        this.onGestureChange = function(event) {
            if (owner.debugGestureChange) {
                Seadragon.Debug.log("TapState::onGestureChange");
            }

            // Do nothing
        };

        this.onGestureEnd = function(event) {
            if (owner.debugGestureEnd) {
                Seadragon.Debug.log("TapState::onGestureEnd");
            }

            // Do nothing
        };

        this.toString = function() {
            return "TapState";
        };
    };

    // Double tapping (two fingers)
    var TwoFingerTapState = function(owner) {
        // Variables
        var owner = owner;
        var timerID = null;

        this.init = function() {
            if (owner.debugInit) {
                Seadragon.Debug.log("TwoFingerTapState::init");
            }
            timerID = setTimeout(this.timeoutHandler, owner.TAP_TIMEOUT);
        };

        this.dispose = function() {
            if (owner.debugDispose) {
                Seadragon.Debug.log("TwoFingerTapState::dispose");
            }
            if (timerID) {
                clearTimeout(timerID);
            }
        };

        this.timeoutHandler = function() {
            if (timerID) {
                clearTimeout(timerID);
                timerID = null;
            }

            owner.setCurrentState(owner.DRAG_STATE);
        };

        // Touch
        this.onTouchStart = function(event) {
            if (owner.debugTouchStart) {
                Seadragon.Debug.error("TwoFingerTapState::onTouchStart");
            }

            owner.setCurrentState(owner.PINCH_STATE);
        };

        this.onTouchMove = function(event) {
            if (owner.debugTouchMove) {
                Seadragon.Debug.log("TwoFingerTapState::onTouchMove");
            }

            // Go to dragging state if touch moved too far (threshold)
            var newTouchPixelOrigin = owner.getTouchPixelOrigin(event);
            var distance = newTouchPixelOrigin.distanceTo(owner.touchPixelOrigin);
            //Seadragon.Debug.log("TwoFingerTapState::onTouchMove distance:" + distance);

            if (distance > owner.TAP_DISTANCE_TRESHOLD) {
                owner.setCurrentState(owner.DRAG_STATE);
            }
        };

        this.onTouchEnd = function(event) {
            if (owner.debugTouchEnd) {
                Seadragon.Debug.log("TwoFingerTapState::onTouchEnd");
            }

            var numTouches = event.touches.length;

            if (numTouches == 0) {
                var viewport = viewer.viewport;
                var touchPointOrigin = viewport.pointFromPixel(owner.touchPixelOrigin);
                viewport.zoomBy(owner.DOUBLE_TAP_ZOOM_FACTOR, touchPointOrigin);
                viewport.ensureVisible();

                // TODO Is this the correct zoom value
                owner.zoom = viewport.getZoom();

                owner.setCurrentState(owner.BASE_STATE);
            }
        };

        // Gestures
        this.onGestureStart = function(event) {
            if (owner.debugGestureStart) {
                Seadragon.Debug.log("TwoFingerTapState::onGestureStart");
            }

            // Do nothing
        };

        this.onGestureChange = function(event) {
            if (owner.debugGestureChange) {
                Seadragon.Debug.log("TwoFingerTapState::onGestureChange");
            }

            // Do nothing
        };

        this.onGestureEnd = function(event) {
            if (owner.debugGestureEnd) {
                Seadragon.Debug.log("TwoFingerTapState::onGestureEnd");
            }

            // Do nothing
        };

        this.toString = function() {
            return "TwoFingerTapState";
        };
    };

    // Dragging
    var DragState = function(owner) {
        // Variables
        var owner = owner;

        this.init = function() {
            if (owner.debugInit) {
                Seadragon.Debug.log("DragState::init");
            }

            // Do nothing
        };

        this.dispose = function() {
            if (owner.debugDispose) {
                Seadragon.Debug.log("DragState::dispose");
            }

            // Do nothing
        };

        // Touch
        this.onTouchStart = function(event) {
            if (owner.debugTouchStart) {
                Seadragon.Debug.log("DragState::onTouchStart");
            }

            var numTouches = event.touches.length;

            // If detect another touch switch to pinching gesture
            if (numTouches >= 1) {
                owner.setCurrentState(owner.PINCH_STATE);
                return;
            }

            if (numTouches >= 2) {
                Seadragon.Debug.error("TapState::onTouchEnd (numTouches ==" + numTouches + ") >= 2");
            }
        };

        this.onTouchMove = function(event) {
            if (owner.debugTouchMove) {
                Seadragon.Debug.log("DragState::onTouchMove");
            }

            // Cache number of touches
            var numTouches = event.touches.length;

            if (numTouches >= 2) {
                owner.setCurrentState(owner.PINCH_STATE);
                return;
            }

            // Update touch origin in page coordinates
            // by linearly interpolating over all touch points
            var newTouchPixelOrigin = owner.getTouchPixelOrigin(event);

            var viewport = owner.viewer.viewport;
            var containerSize = viewport.getContainerSize();
            var viewportBounds = viewport.getBounds();

            // Normalize distance in page coordinates
            var dx = (newTouchPixelOrigin.x - owner.touchPixelOrigin.x) / containerSize.x;
            var dy = (newTouchPixelOrigin.y - owner.touchPixelOrigin.y) / containerSize.y;

            // TODO
            var targetX = owner.lastTouchPoint.x - (dx * viewportBounds.width * 1.2);
            var targetY = owner.lastTouchPoint.y - (dy * viewportBounds.height * 1.2);
            var target = new Seadragon.Point(targetX, targetY);

            viewport.panTo(target);
            viewport.ensureVisible();
        };

        this.onTouchEnd = function(event) {
            if (owner.debugTouchEnd) {
                Seadragon.Debug.log("DragState::onTouchEnd");
            }

            var numTouches = event.touches.length;

            // FIXME
            if (numTouches == 0) {
                owner.setCurrentState(owner.BASE_STATE);
            }
        };

        // Gestures
        this.onGestureStart = function(event) {
            if (owner.debugGestureStart) {
                Seadragon.Debug.log("DragState::onGestureStart");
            }

            // Do nothing
        };

        this.onGestureChange = function(event) {
            if (owner.debugGestureChange) {
                Seadragon.Debug.log("DragState::onGestureChange");
            }


            // Switch to pinching state if the scale
            // change is more than a certain threshold, e.g. 10%.
            /*
            var deltaScale = Math.abs(event.scale - 1);
            if (deltaScale > owner.SCALE_TRESHOLD) {
            owner.setCurrentState(owner.PINCH_STATE);
            return;
            }
            */
        };

        this.onGestureEnd = function(event) {
            if (owner.debugGestureEnd) {
                Seadragon.Debug.log("DragState::onGestureEnd");
            }

            // Do nothing
        };

        this.toString = function() {
            return "DragState";
        };
    };

    // Pinching
    var PinchState = function(owner) {
        // Variables
        var owner = owner;

        this.init = function() {
            if (owner.debugInit) {
                Seadragon.Debug.log("PinchState::init");
            }

            // Do nothing
        };

        this.dispose = function() {
            if (owner.debugGestureEnd) {
                Seadragon.Debug.log("PinchState::dispose");
            }

            // Do nothing
        };

        // Touch
        this.onTouchStart = function(event) {
            if (owner.debugTouchStart) {
                Seadragon.Debug.log("PinchState::onTouchStart");
            }

            // Do nothing
        };

        this.onTouchMove = function(event) {
            if (owner.debugTouchMove) {
                Seadragon.Debug.log("PinchState::onTouchMove");
            }

            owner.touchPixelOrigin = owner.getTouchPixelOrigin(event);
        };

        this.onTouchEnd = function(event) {
            if (owner.debugTouchEnd) {
                Seadragon.Debug.log("PinchState::onTouchEnd");
            }

            var numTouches = event.touches.length;

            // If there's only finger left on the surface,
            // assume we're back to dragging.
            /*
            if (numTouches == 1) {
            owner.setCurrentState(owner.DRAG_STATE)
            return;
            }
            */

            // If all all fingers left the surface, we're back
            // to square one.
            if (numTouches == 0) {
                owner.setCurrentState(owner.BASE_STATE);
            }
        };

        // Gestures
        this.onGestureStart = function(event) {
            if (owner.debugGestureStart) {
                Seadragon.Debug.log("PinchState::onGestureStart");
            }
        };

        this.onGestureChange = function(event) {
            if (owner.debugGestureChange) {
                Seadragon.Debug.log("PinchState::onGestureChange: event.scale" + event.scale);
            }

            // Switch to pinching state if the scale
            // change is more than a certain threshold, e.g. 10%.
            /*
            if (Math.abs(event.scale - 1) <= owner.SCALE_TRESHOLD) {
            owner.setCurrentState(owner.DRAG_STATE);
            return;
            }
            */

            var viewport = owner.viewer.viewport;
            var touchPointOrigin = viewport.pointFromPixel(owner.touchPixelOrigin);
            viewport.zoomTo(owner.zoom * event.scale, touchPointOrigin, true);
            viewport.ensureVisible();
        };

        this.onGestureEnd = function(event) {
            if (owner.debugGestureEnd) {
                Seadragon.Debug.log("PinchState::onGestureEnd");
            }
        };

        this.toString = function() {
            return "PinchState";
        };
    };

    // Fields
    this.viewer = viewer;
    this.touchPixelOrigin = null; // Seadragon.Point with page coordinates
    this.lastTouchPoint = null;
    this.lastZoom = 1.0;

    this.debugInit = false;
    this.debugDispose = false;
    this.debugTouchStart = false;
    this.debugTouchMove = false;
    this.debugTouchEnd = false;
    this.debugGestureStart = false;
    this.debugGestureChange = false;
    this.debugGestureEnd = false;

    if (!viewer) {
        Seadragon.Debug.error("Seadragon.TouchController: viewer is null.");
    }

    // Properties
    var currentState = null;

    this.getCurrentState = function() {
        return currentState;
    };

    this.setCurrentState = function(value) {
        if (!value) {
            Seadragon.Debug.error("Seadragon.TouchController::setCurrentState: argument is null.");
        }

        if (currentState) {
            currentState.dispose();
        }

        currentState = value;
        currentState.init();
    };

    this.getTouchPixelOrigin = function(event) {
        var numTouches = event.touches.length;
        var touchPixelOrigin = new Seadragon.Point(0, 0);

        for (var i = 0; i < numTouches; i++) {
            var touch = event.touches[i];
            touchPixelOrigin.x += touch.pageX;
            touchPixelOrigin.y += touch.pageY;
        }

        touchPixelOrigin.x /= numTouches;
        touchPixelOrigin.y /= numTouches;

        return touchPixelOrigin;
    };

    // Touch handlers
    this.onTouchStart = function(event) {
        event.preventDefault();
        currentState.onTouchStart(event);
    };

    this.onTouchMove = function(event) {
        event.preventDefault();
        currentState.onTouchMove(event);
    };

    this.onTouchEnd = function(event) {
        event.preventDefault();
        currentState.onTouchEnd(event);
    };

    // Gesture handlers
    this.onGestureStart = function(event) {
        event.preventDefault();
        currentState.onGestureStart(event);
    };

    this.onGestureChange = function(event) {
        event.preventDefault();
        currentState.onGestureChange(event);
    };

    this.onGestureEnd = function(event) {
        event.preventDefault();
        currentState.onGestureEnd(event);
    };

    // Constructor
    this.TAP_ZOOM_FACTOR = 2.0;
    this.DOUBLE_TAP_ZOOM_FACTOR = 0.5;
    this.SCALE_TRESHOLD = 0.02; // percent
    this.TAP_DISTANCE_TRESHOLD = 5; // pixels
    this.TAP_TIMEOUT = 800; // milliseconds

    this.BASE_STATE = new BaseState(this);
    this.TAP_STATE = new TapState(this);
    this.TWO_FINGER_TAP_STATE = new TwoFingerTapState(this);
    this.PINCH_STATE = new PinchState(this);
    this.DRAG_STATE = new DragState(this);

    this.setCurrentState(this.BASE_STATE);

    if (viewer && viewer.drawer && viewer.drawer.elmt) {
        var node = viewer.drawer.elmt;
        Seadragon.Utils.addEvent(node, "touchstart", this.onTouchStart);
        Seadragon.Utils.addEvent(node, "touchmove", this.onTouchMove);
        Seadragon.Utils.addEvent(node, "touchend", this.onTouchEnd);

        Seadragon.Utils.addEvent(node, "gesturestart", this.onGestureStart);
        Seadragon.Utils.addEvent(node, "gesturechange", this.onGestureChange);
        Seadragon.Utils.addEvent(node, "gestureend", this.onGestureEnd);
    } else {
        Seadragon.Debug.error("Could not set up TouchController.");
    }
};
